/*
 * getopt - POSIX like getopt for Windows console Application
 *
 * win-c - Windows Console Library
 * Copyright (c) 2015 Koji Takami
 * Released under the MIT license
 * https://github.com/takamin/win-c/blob/master/LICENSE
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Koji Takami
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/* Minor edits by John Mechalas <john.p.mechalas@intel.com> */

#include "getopt.h"
#include <stdio.h>
#include <string.h>

int _getopt_(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);

char* optarg = 0;
int optind = 1;
int opterr = 1;
int optopt = 0;

int postpone_count = 0;
int nextchar = 0;

static void postpone(int argc, char* const argv[], int index) {
	char** nc_argv = (char**)argv;
	char* p = nc_argv[index];
	int j = index;
	for (; j < argc - 1; j++) {
		nc_argv[j] = nc_argv[j + 1];
	}
	nc_argv[argc - 1] = p;
}

static int postpone_noopt(int argc, char* const argv[], int index) {
	int i = index;
	for (; i < argc; i++) {
		if (*(argv[i]) == '-') {
			postpone(argc, argv, index);
			return 1;
		}
	}
	return 0;
}

static int _getopt_ (int argc, char* const argv[], const char* optstring, const struct option* longopts, int* longindex)
{
	while (1) {
		int c;
		const char* optptr = 0;
		if (optind >= argc - postpone_count) {
			c = 0;
			optarg = 0;
			break;
		}
		c = *(argv[optind] + nextchar);
		if (c == '\0') {
			nextchar = 0;
			++optind;
			continue;
		}
		if (nextchar == 0) {
			if (optstring[0] != '+' && optstring[0] != '-') {
				while (c != '-') {
					/* postpone non-opt parameter */
					if (!postpone_noopt(argc, argv, optind)) {
						break; /* all args are non-opt param */
					}
					++postpone_count;
					c = *argv[optind];
				}
			}
			if (c != '-') {
				if (optstring[0] == '-') {
					optarg = argv[optind];
					nextchar = 0;
					++optind;
					return 1;
				}
				break;
			}
			else {
				if (strcmp(argv[optind], "--") == 0) {
					optind++;
					break;
				}
				++nextchar;
				if (longopts != 0 && *(argv[optind] + 1) == '-') {
					char const* spec_long = argv[optind] + 2;
					char const* pos_eq = strchr(spec_long, '=');
					/* Changed to size_t to eliminate compiler warning. -JM */
					size_t spec_len = (pos_eq == NULL) ? strlen(spec_long) : pos_eq - spec_long; 
					int index_search = 0;
					int index_found = -1;
					const struct option* optdef = 0;
					const struct option* orig_longopts= longopts;

					/* First try to find a perfect match. -JM  */
					while (longopts->name != 0 ) {
						if (strncmp(spec_long, longopts->name, spec_len) == 0 && 
							strlen(longopts->name) == spec_len ) {

							optdef = longopts;
							index_found = index_search;
							break;
						}
						longopts++;
						index_search++;
					}
					if (index_found == -1) {
						index_search = 0;
						longopts = orig_longopts;
						while (longopts->name != 0) {
							if (strncmp(spec_long, longopts->name, spec_len) == 0) {
								if (optdef != 0) {
									if (opterr) {
										fprintf(stderr, "ambiguous option: %s\n", spec_long);
									}
									return '?';
								}
								optdef = longopts;
								index_found = index_search;
							}
							longopts++;
							index_search++;
						}
						if (optdef == 0) {
							if (opterr) {
								fprintf(stderr, "no such a option: %s\n", spec_long);
							}
							return '?';
						}
					}

					switch (optdef->has_arg) {
					case no_argument:
						optarg = 0;
						if (pos_eq != 0) {
							if (opterr) {
								fprintf(stderr, "no argument for %s\n", optdef->name);
							}
							return '?';
						}
						break;
					case required_argument:
						if (pos_eq == NULL) {
							++optind;
							optarg = argv[optind];
						}
						else {
							optarg = (char*)pos_eq + 1;
						}
						if (optarg == NULL) {
							fprintf(stderr,
								"%s: option requires an argument --%s\n",
								argv[0], optdef->name);
							return '?';
						}
						break;
					}
					++optind;
					nextchar = 0;
					if (longindex != 0) {
						*longindex = index_found;
					}
					if (optdef->flag != 0) {
						*optdef->flag = optdef->val;
						return 0;
					}
					return optdef->val;
				}
				continue;
			}
		}
		optptr = strchr(optstring, c);
		if (optptr == NULL) {
			optopt = c;
			if (opterr) {
				fprintf(stderr,
					"%s: invalid option -- %c\n",
					argv[0], c);
			}
			++nextchar;
			return '?';
		}
		if (*(optptr + 1) != ':') {
			nextchar++;
			if (*(argv[optind] + nextchar) == '\0') {
				++optind;
				nextchar = 0;
			}
			optarg = 0;
		}
		else {
			nextchar++;
			if (*(argv[optind] + nextchar) != '\0') {
				optarg = argv[optind] + nextchar;
			}
			else {
				++optind;
				if (optind < argc - postpone_count) {
					optarg = argv[optind];
				}
				else {
					optopt = c;
					if (opterr) {
						fprintf(stderr,
							"%s: option requires an argument -- %c\n",
							argv[0], c);
					}
					if (optstring[0] == ':' ||
						(optstring[0] == '-' || optstring[0] == '+') &&
						optstring[1] == ':')
					{
						c = ':';
					}
					else {
						c = '?';
					}
				}
			}
			++optind;
			nextchar = 0;
		}
		return c;
	}

	/* end of option analysis */

	/* fix the order of non-opt params to original */
	while ((argc - optind - postpone_count) > 0) {
		postpone(argc, argv, optind);
		++postpone_count;
	}

	nextchar = 0;
	postpone_count = 0;
	return -1;
}

int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex)
{
	return _getopt_(argc, argv, optstring, longopts, longindex);
}

int getopt(int argc, char * const argv[], const char *optstring)
{
	return _getopt_(argc, argv, optstring, NULL, NULL);
}
